home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C
/
Applications
/
Fixation 1.3
/
net.c
< prev
next >
Wrap
Text File
|
1995-09-16
|
75KB
|
2,747 lines
/*----------------------------------------------------------------------------
net.c
This reusable and reentrant module handles low-level communications with
TCP/IP network servers, using a simple "net ASCII" command/response
stream model. It also exports several functions for using FTP data streams,
and several functions for doing domain name resolver tasks.
The module hides the complexity and peculiarities of Open Transport and the
MacTCP device driver. If Open Transport is available and we are running in
native PowerPC mode, it is used. Otherwise, MacTCP is used.
The module also manages many of the common protocol details of communicating
with net ASCII servers:
Handling initial server "hello" messages.
Handling final server "QUIT" commands and all the details of graceful
asynchronous stream close operations.
Mapping between CR line terminators and CRLF line terminators.
Decoding numeric server response codes.
Skipping server comments (response lines with "-" following the response code).
Supplying terminating "." lines when sending blocks of text.
Recognizing and discarding terminating "." lines when reading blocks of text.
Mapping between leading "." characters and leading ".." characters on text lines.
The module emphasizes simplicity at the expense of power. There are many things
you can do by calling Open Transport or MacTCP directly which you cannot do with
this module. The module is *not* a general purpose interface to Open Transport or
MacTCP designed to meet the needs of all possible Mac TCP/IP networking programs.
It is only a convenient interface for writing clients for typical simple net ASCII
servers.
The following mandatory functions handle initialization, idle time, and
termination tasks.
NetInit - Initialize the module.
NetIdle - Handle idle time tasks.
NetTerm - Terminate the module.
The following functions work with Net ASCII command/response streams.
NetOpen - Open a stream.
NetClose - Close a stream.
NetCommand - Send a command and get the response.
NetGetExtraResponse - Get an extra command response.
NetBatchedCommands - Send several commands in a batch and
get and process the responses.
NetPutText - Send command text.
NetGetText - Get response text.
The following functions work with FTP data streams. They are used by
the ftp.c module.
.
NetFTPDataPassiveOpen - Open an FTP passive data stream.
NetFTPDataClose - Close an FTP data stream.
NetFTPDataWaitForConnection - Wait for the FTP server to open
its end of an FTP passive data stream.
NetGetFTPData - Get FTP data.
NetSendFTPData - Send FTP data.
The following functions handle simple domain name resolver and related tasks.
NetGetMyAddr - Get my IP address.
NetGetMyAddrStr - Get my IP address as a dotted-decimal string.
NetGetMyName - Get my domain name.
NetNameToAddr - Convert a domain name to an IP address.
NetAddrToName - Convert an IP address to a domain name.
Finally, the module exports the following utility functions:
NetMacTCPDNROperationInProgress - Determine if a MacTCP DNR operation
is in progress.
NetGetServerErrInfo - Get server error information.
NetGetStreamStats - Get stream stats (bytes in/out).
NetHaveOT - Determine whether we are using Open Transport or MacTCP.
A "stream" is an abstraction representing a bidirectional network connection
to a net ASCII TCP/IP server.
With Open Transport, the notion of a "stream" in this module is basically
equivalent to a "TCP endpoint". The "NetOpen" function opens an endpoint,
binds it to a TCP port, and creates a connection. The "NetClose" function
does an orderly disconnect and closes the endpoint.
With MacTCP, the notion of a "stream" in this module combines the
separate MacTCP notions of "stream" and "connection". When calling MacTCP
directly, you "create" and "release" streams and "open" and "close" connections.
These operations are combined in the net.c module. The "NetOpen" function both
creates a stream and opens a connection. The "NetClose" function both
closes the connection and releases the stream.
A stream is represented as a variable of type "NetStreamRef". These stream
references are opaque. You may copy them and pass them as parameters to
functions in net.c, but you are prohibited from accessing the contents of
the memory blocks pointed to by the references. Only the functions in
net.c are permitted to manipulate the contents of these blocks.
The functions return a value of type OSErr as the function result:
noErr no error occurred
netOpenDriverErr NetInit could not initialize the network driver
netOpenStreamErr stream open failed
netLostConnectionErr stream was unexpectedly closed or aborted by server
netDNRErr DNR failed to resolve name or address
netTruncatedErr incoming text was truncated
other any other OS or toolbox error
If any error occurs, the stream is aborted before returning to the caller.
"Aborted" means that the server connection is closed abruptly, without going
through the usual orderly TCP stream teardown process. The stream is also
deallocated. In this case, you must not attempt to reuse the stream reference,
since the stream no longer exists. You must perform careful error checking.
The "expected" Open Transport and MacTCP error codes are translated into either
regular MacOS error codes (e.g., kENOMEMErr -> memFullErr) or into the
special "net" error codes (e.g., authNameErr -> netDNRErr). This
gives the clients of this module a uniform set of error codes which are the
same for both Open Transport and MacTCP. Open Transport and MacTCP error
codes which are not translated are "unexpected" errors which should not
occur, and if they do, indicate an error in the code in this module.
Copyright © 1994-1995, Northwestern University.
----------------------------------------------------------------------------*/
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include "MyMacTCP.h"
#include "OpenTransport.h"
#include "OpenTptInternet.h"
#include "def.h"
#include "net.h"
#include "memutil.h"
#include "strutil.h"
/* Constants. */
#define kBufSize (4*1024) /* buffer size */
#define kBufSizeDiv2 (kBufSize/2) /* buffer size div 2 */
#define kMinTCPBufSize (16*1024) /* minimum MacTCP stream buffer size */
/* Types. */
typedef struct TStream { /* Stream info */
TCPiopb pBlock; /* MacTCP parameter block (must be first, unused with
Open Transport) */
/* Fields used with both Open Transport and MacTCP. */
struct TStream *next; /* pointer to next stream in list of all open streams */
struct TStream *nextClose; /* pointer to next stream in list of closing streams */
struct TStream *nextFree; /* pointer to next stream in list of available preallocated
streams */
unsigned long serverAddr; /* IP address of server */
unsigned short serverPort; /* port number on server */
unsigned short localPort; /* local port number */
char buf[kBufSize]; /* data buffer */
char *in; /* pointer to next location in data buffer in
which incoming network bytes should be stored */
char *out; /* pointer to next location in data buffer from
which bytes should be removed and delivered
to our clients (note: we always have out <= in) */
Boolean fromFreeList; /* true if this buffer was obtained from the free list */
CStr255 command; /* last command sent on stream */
CStr255 response; /* last response received on stream */
long responseCode; /* last response code received on stream */
Boolean closing; /* true when closing stream */
Boolean otherSideHasClosed; /* true when other side has closed its end of
the stream */
Boolean weHaveClosed; /* true when we have closed our end of the stream */
Boolean release; /* true when stream should be released */
long bytesIn; /* number of bytes received on stream */
long bytesOut; /* number of bytes sent on stream */
/* Fields used only with Open Transport. */
EndpointRef ref; /* endpoint reference */
Boolean complete; /* true when asynch operation has completed */
OTEventCode code; /* event code */
OTResult result; /* result */
void *cookie; /* cookie */
TCall *call; /* pointer to call structure */
TBind *bindReq; /* pointer to bind request structure */
TBind *bindRet; /* pointer to bind return structure */
/* Fields used only with MacTCP. */
long myA5; /* our A5 register */
StreamPtr tcpStream; /* MacTCP stream pointer */
struct wdsEntry wds[2]; /* MacTCP write data structure */
char mactcpBuf[1]; /* MacTCP buffer - size depends on MTU (must be last) */
} TStream, *TStreamPtr;
typedef struct TMyOTInetSvcInfo { /* Open Transport Internet services provider info */
InetSvcRef ref; /* provider reference */
Boolean complete; /* true when asynch operation has completed */
OTResult result; /* result code */
void *cookie; /* cookie */
} TMyOTInetSvcInfo;
/* Global variables. */
static Boolean gHaveOT; /* true if we have Open Transport */
static NetGiveTimeFunction gGiveTime; /* pointer to GiveTime function */
static NetLogFunction gLog; /* pointer to logging function, or nil if none */
static TStreamPtr gAll = nil; /* pointer to list of open streams */
static TStreamPtr gClose = nil; /* pointer to list of closing streams */
static TStreamPtr gFree = nil; /* pointer to list of available preallocated
stream buffers */
static long gStreamBufSize; /* stream buffer size */
static short gRefNum; /* MacTCP driver reference number */
static long gTCPBufSize; /* TCP buffer size for MacTCP */
static Boolean gMacTCPDNROperationInProgress = false;
/* True when MacTCP DNR operation in progress */
static ResultUPP gMacTCPDNRResultProcUPP = nil;
static TCPIOCompletionUPP gMacTCPCloseStreamCompletionRoutineUPP = nil;
static UniversalProcPtr gOldExitToShellUPP;
static UniversalProcPtr gMyExitToShellUPP;
/*----------------------------------------------------------------------------
TranslateErrorCode
Translate error codes.
Entry: err = Open Transport, MacTCP, or OS error code.
Exit: function result = translated error code.
----------------------------------------------------------------------------*/
static OSErr TranslateErrorCode (OSErr err)
{
switch (err) {
case kENOMEMErr:
return memFullErr;
case noNameServer:
case authNameErr:
case noAnsErr:
case dnrErr:
case nameSyntaxErr:
return netDNRErr;
case openFailed:
return netOpenStreamErr;
default:
return err;
}
}
/*----------------------------------------------------------------------------
LogMessage
Log a message.
Entry: s = pointer to stream.
logEntryType =
'C' if command.
'R' if response.
' ' if open/close operation.
str = command or response string or open/close message string.
----------------------------------------------------------------------------*/
static void LogMessage (TStreamPtr s, char logEntryType, char *str)
{
if (gLog != nil) (*gLog)(logEntryType, s->serverAddr, s->serverPort,
s->localPort, str);
}
/*----------------------------------------------------------------------------
NewStreamBuffer
Allocate a stream buffer.
Exit: function result = error code.
*s = pointer to new stream buffer.
----------------------------------------------------------------------------*/
static OSErr NewStreamBuffer (TStreamPtr *s)
{
OSErr err = noErr;
if (gFree == nil) {
err = MyNewPtr(gStreamBufSize, s);
if (err != noErr) return err;
} else {
*s = gFree;
gFree = gFree->nextFree;
memset(*s, 0, sizeof(TStream));
(*s)->fromFreeList = true;
}
if (!gHaveOT) (*s)->myA5 = SetCurrentA5();
return noErr;
}
/*----------------------------------------------------------------------------
DisposeStreamBuffer
Dispose a stream buffer.
Entry: s = pointer to stream buffer.
----------------------------------------------------------------------------*/
static void DisposeStreamBuffer (TStreamPtr s)
{
if (s == nil) return;
if (gHaveOT) {
if (s->call != nil) OTFree(s->call, T_CALL);
if (s->bindReq != nil) OTFree(s->bindReq, T_BIND);
if (s->bindRet != nil) OTFree(s->bindRet, T_BIND);
}
if (s->fromFreeList) {
s->nextFree = gFree;
gFree = s;
} else {
MyDisposePtr(s);
}
}
/*----------------------------------------------------------------------------
MyOTStreamNotifyProc
Open Transport notifier proc for TCP streams.
Entry: s = pointer to stream.
code = OT event code.
result = OT result.
cookie = OT cookie.
----------------------------------------------------------------------------*/
static pascal void MyOTStreamNotifyProc (TStreamPtr s, OTEventCode code,
OTResult result, void *cookie)
{
switch (code) {
case T_DISCONNECT:
/* Other side has aborted. */
s->otherSideHasClosed = true;
s->complete = true;
break;
case T_ORDREL:
/* Other side has closed. Close our side if necessary. */
s->otherSideHasClosed = true;
s->complete = true;
OTRcvOrderlyDisconnect(s->ref);
if (!s->weHaveClosed) {
OTSndOrderlyDisconnect(s->ref);
s->weHaveClosed = true;
}
if (s->closing) s->release = true;
break;
case T_DATA:
if (s->closing) {
/* Consume and discard response to "QUIT" comand. */
do {
result = OTRcv(s->ref, s->buf, kBufSize, nil);
} while (result >= 0);
}
break;
case T_OPENCOMPLETE:
case T_BINDCOMPLETE:
case T_CONNECT:
case T_PASSCON:
s->complete = true;
s->code = code;
s->result = result;
s->cookie = cookie;
break;
}
}
/*----------------------------------------------------------------------------
MyOTStreamWait
Wait for an asynchronous Open Transport stream call to complete.
Entry: s = pointer to stream.
returnImmediatelyOnError = true to return immediately if
the GiveTime function returns an error.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr MyOTStreamWait (TStreamPtr s, Boolean returnImmediatelyOnError)
{
OSErr err = noErr, giveTimeErr = noErr;
do {
giveTimeErr = (*gGiveTime)();
if (err == noErr) err = giveTimeErr;
if (err != noErr && returnImmediatelyOnError) return err;
} while (!s->complete);
if (err == noErr) err = s->result;
return err;
}
/*----------------------------------------------------------------------------
MyOTInetSvcNotifyProc
Open Transport notifier proc for an Internet services provider.
Entry: svcIfno = pointer to MyOTInetSvcInfo struct.
code = OT event code.
result = OT result.
cookie = OT cookie.
----------------------------------------------------------------------------*/
static pascal void MyOTInetSvcNotifyProc (TMyOTInetSvcInfo *svcInfo, OTEventCode code,
OTResult result, void *cookie)
{
switch (code) {
case T_OPENCOMPLETE:
case T_DNRSTRINGTOADDRCOMPLETE:
case T_DNRADDRTONAMECOMPLETE:
svcInfo->complete = true;
svcInfo->result = result;
svcInfo->cookie = cookie;
break;
}
}
/*----------------------------------------------------------------------------
MyOTInetSvcWait
Wait for an asynchronous Open Transport Internet services call to complete.
Entry: svcInfo = pointer to TMyOTInetSvcInfo struct.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr MyOTInetSvcWait (TMyOTInetSvcInfo *svcInfo)
{
OSErr err = noErr;
do {
err = (*gGiveTime)();
if (err != noErr) return err;
} while (!svcInfo->complete);
return svcInfo->result;
}
/*----------------------------------------------------------------------------
MyOTOpenInetServices
Open an Internet services provider.
Entry: svcInfo = pointer to TMyOTInetSvcInfo struct for this
provider.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr MyOTOpenInetServices (TMyOTInetSvcInfo *svcInfo)
{
OSErr err = noErr;
svcInfo->complete = false;
err = OTAsyncOpenInternetServices(kDefaultInternetServicesPath, 0,
MyOTInetSvcNotifyProc, svcInfo);
if (err != noErr) return err;
err = MyOTInetSvcWait(svcInfo);
if (err != noErr) return err;
svcInfo->ref = svcInfo->cookie;
return noErr;
}
/*----------------------------------------------------------------------------
InitMacTCPParamBlock
Initialize a MacTCP parameter block.
Entry: pBlock = pointer to parameter block.
csCode = MacTCP control code (TCPCreate, etc.).
----------------------------------------------------------------------------*/
static void InitMacTCPParamBlock (TCPiopb *pBlock, short csCode)
{
memset(pBlock, 0, sizeof(TCPiopb));
pBlock->ioResult = 1;
pBlock->ioCRefNum = gRefNum;
pBlock->csCode = csCode;
}
/*----------------------------------------------------------------------------
CallMacTCP
Call MacTCP and wait for the call to complete.
Entry: s = pointer to stream.
returnImmediatelyOnError = true to return immediately if
the GiveTime function returns an error.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr CallMacTCP (TStreamPtr s, Boolean returnImmediatelyOnError)
{
OSErr err = noErr, giveTimeErr = noErr;
PBControlAsync((ParmBlkPtr)&s->pBlock);
do {
giveTimeErr = (*gGiveTime)();
if (err == noErr) err = giveTimeErr;
if (err != noErr && returnImmediatelyOnError) return err;
} while (s->pBlock.ioResult > 0);
if (err == noErr) err = s->pBlock.ioResult;
if (err == connectionClosing || err == connectionDoesntExist ||
err == connectionTerminated) s->otherSideHasClosed = true;
return err;
}
/*----------------------------------------------------------------------------
MacTCPDNRResultProc
MacTCP domain name resolver completion routine.
Entry: hInfoPtr = pointer to hostInfo struct.
userDataPtr = pointer to user data.
----------------------------------------------------------------------------*/
static pascal void MacTCPDNRResultProc (struct hostInfo *hInfoPtr, char *userDataPtr)
{
*(Boolean*)userDataPtr = true;
}
/*----------------------------------------------------------------------------
MacTCPCloseStreamCompletionRoutine
This is the asynchronous MacTCP completion routine used to close streams.
This completion routine chains to itself to do the following tasks
involved in gracefully tearing down a stream in the background:
Wait for QUIT command send to complete (the send is initiated by the
NetClose function below).
Read incoming data until an error occurs (signalling that the
server has closed its end of the connection).
Close our end of the connection.
Entry: pBlock = pointer to MacTCP parameter block.
----------------------------------------------------------------------------*/
static void MacTCPCloseStreamCompletionRoutine (TCPiopb *pBlock)
{
TStreamPtr s;
OSErr err = noErr;
long savedA5;
s = (TStreamPtr)pBlock;
savedA5 = SetA5(s->myA5);
if (s->pBlock.csCode == TCPSend) {
/* The QUIT command has been sent. Start the first receive. */
err = s->pBlock.ioResult;
if (err != noErr) {
s->release = true;
SetA5(savedA5);
return;
}
InitMacTCPParamBlock(&s->pBlock, TCPRcv);
s->pBlock.csParam.receive.rcvBuff = s->buf;
s->pBlock.csParam.receive.rcvBuffLen = kBufSize;
} else if (s->pBlock.csCode == TCPRcv) {
/* A receive operation has finished. If there was no error, start another
receive. If an error occurred, it was because the server has closed
its side of the connection. In this case, if we have already closed
our side of the connection, set the "release" flag to signal that
we can now release the stream and dispose of the queue element. If
we have not yet closed our side, we start the TCPClose. */
err = s->pBlock.ioResult;
if (err == noErr) {
InitMacTCPParamBlock(&s->pBlock, TCPRcv);
s->pBlock.csParam.receive.rcvBuff = s->buf;
s->pBlock.csParam.receive.rcvBuffLen = kBufSize;
} else {
s->otherSideHasClosed = true;
if (s->weHaveClosed) {
s->release = true;
SetA5(savedA5);
return;
} else {
InitMacTCPParamBlock(&s->pBlock, TCPClose);
}
}
} else if (s->pBlock.csCode == TCPClose) {
/* Our close has finished. If the other side has also closed,
set the "release" flag to signal that we can now release
the stream and dispose of the queue element. If the other
side has not yet closed, issue a receive operation and wait
for the other side to close. */
s->weHaveClosed = true;
if (s->otherSideHasClosed) {
s->release = true;
SetA5(savedA5);
return;
} else {
InitMacTCPParamBlock(&s->pBlock, TCPRcv);
s->pBlock.csParam.receive.rcvBuff = s->buf;
s->pBlock.csParam.receive.rcvBuffLen = kBufSize;
}
}
/* Issue the next PBControl call in the chain. */
s->pBlock.ioCompletion = gMacTCPCloseStreamCompletionRoutineUPP;
s->pBlock.tcpStream = s->tcpStream;
s->pBlock.ioResult = 1;
err = PBControlAsync((ParmBlkPtr)&s->pBlock);
if (err != noErr) s->release = true;
SetA5(savedA5);
}
/*----------------------------------------------------------------------------
GetMaxTCPMTU
Get the max MTU size.
Exit: function result = max MTU, or 0 if error.
----------------------------------------------------------------------------*/
static long GetMaxTCPMTU (void)
{
UDPiopb iopb;
OSErr err = noErr;
memset(&iopb, 0, sizeof(iopb));
err = NetGetMyAddr(&iopb.csParam.mtu.remoteHost);
if (err != noErr) return 0;
iopb.ioCRefNum = gRefNum;
iopb.csCode = UDPMaxMTUSize;
err = PBControlSync((ParmBlkPtr)&iopb);
if (err != noErr) return 0;
return iopb.csParam.mtu.mtuSize;
}
/*----------------------------------------------------------------------------
DoTCPCreate
Create a stream.
Entry: s = pointer to stream.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoTCPCreate (TStreamPtr s)
{
OSErr err = noErr;
OSStatus oterr;
if (gHaveOT) {
s->complete = false;
err = OTAsyncOpenEndpoint(OTCreateConfiguration(kTCPName), 0,
nil, MyOTStreamNotifyProc, s);
if (err != noErr) return err;
err = MyOTStreamWait(s, false);
if (err != noErr) return err;
if (s->code != T_OPENCOMPLETE) return netOpenStreamErr;
s->ref = s->cookie;
s->call = OTAlloc(s->ref, T_CALL, T_ADDR, &oterr);
err = oterr;
if (err != noErr) goto exit;
s->bindReq = OTAlloc(s->ref, T_BIND, T_ADDR, &oterr);
err = oterr;
if (err != noErr) goto exit;
s->bindRet = OTAlloc(s->ref, T_BIND, T_ADDR, &oterr);
err = oterr;
if (err != noErr) goto exit;
} else {
InitMacTCPParamBlock(&s->pBlock, TCPCreate);
s->pBlock.csParam.create.rcvBuff = s->mactcpBuf;
s->pBlock.csParam.create.rcvBuffLen = gTCPBufSize;
err = CallMacTCP(s, false);
if (err != noErr) return err;
s->tcpStream = s->pBlock.tcpStream;
}
s->next = gAll;
gAll = s;
return noErr;
exit:
OTCloseProvider(s->ref);
return err;
}
/*----------------------------------------------------------------------------
DoTCPRelease
Release a stream.
Entry: s = pointer to stream.
Exit: function result = error code.
Any active connection is also aborted, if necessary, before releasing
the stream.
With MacTCP, the function uses its own TCPiopb parameter block instead
of the one inside the stream block, because the one inside the stream block
may already be in use by some other asynchronous operation.
----------------------------------------------------------------------------*/
static OSErr DoTCPRelease (TStreamPtr s)
{
TCPiopb pBlock;
OSErr err = noErr, giveTimeErr = noErr;
TStreamPtr xs, next, prev = nil;
Boolean abort;
if (s == nil) return noErr;
abort = !s->otherSideHasClosed || !s->weHaveClosed;
if (gHaveOT) {
if (abort) OTSndDisconnect(s->ref, nil);
err = OTCloseProvider(s->ref);
} else {
InitMacTCPParamBlock(&pBlock, TCPRelease);
pBlock.tcpStream = s->tcpStream;
PBControlAsync((ParmBlkPtr)&pBlock);
while (pBlock.ioResult > 0) {
giveTimeErr = (*gGiveTime)();
if (err == noErr) err = giveTimeErr;
}
if (err == noErr) err = pBlock.ioResult;
}
for (xs = gAll; xs != nil; xs = next) {
next = xs->next;
if (xs == s) {
if (prev == nil) {
gAll = next;
} else {
prev->next = next;
}
break;
}
prev = xs;
}
if (gLog != nil) LogMessage(s, ' ', abort ? "Stream aborted." : "Stream released.");
return err;
}
/*----------------------------------------------------------------------------
DoTCPActiveOpen
Open an active stream.
Entry: s = pointer to stream.
addr = IP address of server.
port = port number of service.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoTCPActiveOpen (TStreamPtr s, unsigned long addr,
unsigned short port)
{
OSErr err = noErr;
InetAddress *inetAddr;
if (gHaveOT) {
s->complete = false;
err = OTBind(s->ref, nil, s->bindRet);
if (err != noErr) return err;
err = MyOTStreamWait(s, true);
if (err != noErr) return err;
if (s->code != T_BINDCOMPLETE) return netOpenStreamErr;
inetAddr = (InetAddress*)s->bindRet->addr.buf;
s->localPort = inetAddr->fPort;
OTInitInetAddress((InetAddress*)s->call->addr.buf, port, addr);
s->call->addr.len = sizeof(InetAddress);
s->complete = false;
err = OTConnect(s->ref, s->call, nil);
if (err != noErr && err != kOTNoDataErr)
return s->otherSideHasClosed ? netOpenStreamErr : err;
err = MyOTStreamWait(s, true);
if (err != noErr)
return s->otherSideHasClosed ? netOpenStreamErr : err;
if (s->code != T_CONNECT) return netOpenStreamErr;
err = OTRcvConnect(s->ref, nil);
if (err != noErr) return err;
} else {
InitMacTCPParamBlock(&s->pBlock, TCPActiveOpen);
s->pBlock.tcpStream = s->tcpStream;
s->pBlock.csParam.open.remoteHost = addr;
s->pBlock.csParam.open.remotePort = port;
err = CallMacTCP(s, true);
if (err == noErr) {
s->localPort = s->pBlock.csParam.open.localPort;
} else {
return s->otherSideHasClosed ? netOpenStreamErr : err;
}
}
if (gLog != nil) LogMessage(s, ' ', "Stream opened.");
return noErr;
}
/*----------------------------------------------------------------------------
DoTCPPassiveOpen
Open a passive stream.
Entry: s = pointer to stream.
Exit: function result = error code.
*port = assigned unused local port number.
Note: Unlike the other "DoTCPxxx" functions, DoTCPPassiveOpen is
asynchronous. The passive stream is opened, but the function does not
wait for another host to connect. The function is used by
NetFTPDataPassiveOpen to open passive FTP data streams. The caller must
call NetFTPDataWaitForConnection to wait for the FTP server to connect
to the stream.
----------------------------------------------------------------------------*/
static OSErr DoTCPPassiveOpen (TStreamPtr s, unsigned short *port)
{
OSErr err = noErr;
InetAddress *inetAddr;
if (gHaveOT) {
s->bindReq->addr.len = 0;
s->bindReq->qlen = 1;
s->complete = false;
err = OTBind(s->ref, s->bindReq, s->bindRet);
if (err != noErr) return err;
err = MyOTStreamWait(s, true);
if (err != noErr) return err;
if (s->code != T_BINDCOMPLETE) return netOpenStreamErr;
inetAddr = (InetAddress*)s->bindRet->addr.buf;
*port = s->localPort = inetAddr->fPort;
return noErr;
} else {
InitMacTCPParamBlock(&s->pBlock, TCPPassiveOpen);
s->pBlock.tcpStream = s->tcpStream;
PBControlAsync((ParmBlkPtr)&s->pBlock);
while (s->pBlock.csParam.open.localPort == 0) {
err = (*gGiveTime)();
if (err != noErr) return err;
}
*port = s->localPort = s->pBlock.csParam.open.localPort;
return noErr;
}
}
/*----------------------------------------------------------------------------
DoTCPSend
Send data on a stream.
Entry: s = pointer to stream.
data = pointer to data to send.
len = length of data to send.
push = true to set push data flag. This flag should be set on
the last buffer of data being sent on the stream before
a response from the server is expected. This flag is need
to keep IBM VM/CMS happy. (Note OT handles this internally,
so this is needed only with MacTCP.)
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoTCPSend (TStreamPtr s, Ptr data, unsigned short len, Boolean push)
{
OSErr err = noErr;
OTResult result;
s->in = s->out = s->buf;
if (gHaveOT) {
while (len > 0) {
err = (*gGiveTime)();
if (err != noErr) return err;
result = OTSnd(s->ref, data, len, 0);
if (result >= 0) {
len -= result;
s->bytesOut += result;
} else if (result != kOTFlowErr) {
return s->otherSideHasClosed ? netLostConnectionErr : result;
}
}
return noErr;
} else {
s->wds[0].ptr = data;
s->wds[0].length = len;
InitMacTCPParamBlock(&s->pBlock, TCPSend);
s->pBlock.tcpStream = s->tcpStream;
s->pBlock.csParam.send.wdsPtr = (Ptr)s->wds;
s->pBlock.csParam.send.pushFlag = push;
err = CallMacTCP(s, true);
if (err == noErr) s->bytesOut += len;
return s->otherSideHasClosed ? netLostConnectionErr : err;
}
}
/*----------------------------------------------------------------------------
DoTCPRcv
Reveive data on a stream.
Entry: s = pointer to stream.
data = pointer to data buffer.
*len = length of data buffer.
Exit: function result = error code.
*len = number of bytes received.
----------------------------------------------------------------------------*/
static OSErr DoTCPRcv (TStreamPtr s, Ptr data, unsigned short *len)
{
OSErr err = noErr;
OTResult result;
if (gHaveOT) {
err = (*gGiveTime)();
if (err != noErr) return err;
result = OTRcv(s->ref, data, *len, nil);
if (result >= 0) {
*len = result;
s->bytesIn += result;
return noErr;
} else if (s->otherSideHasClosed) {
return netLostConnectionErr;
} else if (result == kOTNoDataErr) {
*len = 0;
return noErr;
} else {
return result;
}
} else {
InitMacTCPParamBlock(&s->pBlock, TCPRcv);
s->pBlock.tcpStream = s->tcpStream;
s->pBlock.csParam.receive.rcvBuff = StripAddress(data);
s->pBlock.csParam.receive.rcvBuffLen = *len;
err = CallMacTCP(s, true);
*len = s->pBlock.csParam.receive.rcvBuffLen;
if (err == noErr) s->bytesIn += *len;
return err != noErr && s->otherSideHasClosed ? netLostConnectionErr : err;
}
}
/*----------------------------------------------------------------------------
MyExitToShell
ExitToShell trap patch (for MacTCP only).
This patch makes sure that all open streams are closed when ExitToShell
is called (e.g., if the program crashes and you type "es" in MacsBug, or
if you use Command-Option-Escape to force quit the program). This keeps
MacTCP happy. It doesn't always work, but it helps sometimes.
Open Transport has its own mechanisms for doing this, so this patch
is only installed if we are using MacTCP.
----------------------------------------------------------------------------*/
static void MyExitToShell (void)
{
TStreamPtr s;
SetCurrentA5();
SetToolTrapAddress(gOldExitToShellUPP, _ExitToShell);
gLog = nil;
for (s = gAll; s != nil; s = s->next) DoTCPRelease(s);
ExitToShell();
}
/*----------------------------------------------------------------------------
PatchExitToShell
Install a patch on the ExitToShell trap (for MacTCP only).
----------------------------------------------------------------------------*/
static void PatchExitToShell (void)
{
gOldExitToShellUPP = GetToolTrapAddress(_ExitToShell);
gMyExitToShellUPP =
NewRoutineDescriptor((ProcPtr)MyExitToShell, kPascalStackBased, GetCurrentISA());
SetToolTrapAddress(gMyExitToShellUPP, _ExitToShell);
}
/*----------------------------------------------------------------------------
SendCommand
Send a command on a stream.
Entry: s = pointer to stream.
command = C-format command string.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr SendCommand (TStreamPtr s, char *command)
{
OSErr err = noErr;
unsigned short len;
strcpy(s->command, command);
len = strlen(command);
BlockMoveData(command, s->buf, len);
*(s->buf + len) = CR;
*(s->buf + len + 1) = LF;
err = DoTCPSend(s, s->buf, len+2, true);
if (err != noErr) return err;
if (gLog != nil) LogMessage(s, 'C', command);
return noErr;
}
/*----------------------------------------------------------------------------
ReadResponse
Read a command response from a stream.
Entry: s = pointer to stream.
Exit: function result = error code.
*responseCode = server response code.
response = C-format server response string, including the
response code.
Response comment lines are discarded (response lines with a
'-' immediately following the response code).
----------------------------------------------------------------------------*/
static OSErr ReadResponse (TStreamPtr s, long *responseCode, CStr255 response)
{
char *in, *out, *p, *q, *r;
unsigned short len, numUnread;
long num;
OSErr err = noErr;
in = s->in;
out = s->out;
while (true) {
while (out < in) {
for (p = out; p < in; p++) {
if (*p == LF) {
*p = 0;
q = out;
out = p+1;
break;
} else if (*p == CR && p < in-1 && *(p+1) == LF) {
*p = 0;
q = out;
out = p+2;
break;
}
}
if (p >= in) break;
r = q;
if (*r == '-') {
r++;
num = -CrackNum(&r);
} else {
num = CrackNum(&r);
}
if (*r != '-' && num != 0) {
if (p - q > 255) *(q+255) = 0;
strcpy(response, q);
strcpy(s->response, q);
*responseCode = s->responseCode = num;
s->in = in;
s->out = out;
if (gLog != nil) LogMessage(s, 'R', response);
return noErr;
}
}
if (out == in) {
out = in = s->buf;
} else if (in > s->buf + kBufSizeDiv2) {
numUnread = in - out;
if (numUnread > kBufSizeDiv2) {
numUnread = kBufSizeDiv2;
out = in - numUnread;
}
BlockMoveData(out, s->buf, numUnread);
out = s->buf;
in = out + numUnread;
}
len = s->buf + kBufSize - in;
err = DoTCPRcv(s, in, &len);
if (err != noErr) return err;
in += len;
}
}
/*----------------------------------------------------------------------------
MungeOut
Munge a block of text before sending it out to a server.
Entry: text = handle to CR-terminated ASCII text lines.
Warning: the memory block is modified by the function.
The memory block must be unlocked and nonpurgeable.
mungePeriods = true to munge periods.
Exit: function result = error code.
CR line terminators are translated to CRLF. If the text block does
not have a terminating CR at the end of the last line, one is added.
If mungePeriods=true, leading "." characters on lines are mapped to
"..", and a terminating "." on a line by itself is added to the end
of the text block.
----------------------------------------------------------------------------*/
static OSErr MungeOut (Handle text, Boolean mungePeriods)
{
OSErr err = noErr;
char *p, *pEnd, *pStart, *q;
long textLen, oldTextLen;
textLen = MyGetHandleSize(text);
p = *text;
pEnd = p + textLen;
if (textLen == 0 || *(pEnd-1) != CR) {
textLen++;
err = MySetHandleSize(text, textLen);
if (err != noErr) return err;
p = *text;
pEnd = p + textLen;
*(pEnd-1) = CR;
}
oldTextLen = textLen;
if (mungePeriods) {
while (p < pEnd) {
if (*p == '.') textLen++;
while (*p != CR) p++;
p++;
textLen++;
}
textLen += 3;
} else {
for (; p < pEnd; p++) if (*p == CR) textLen++;
}
err = MySetHandleSize(text, textLen);
if (err != noErr) return err;
pStart = *text;
p = pStart + oldTextLen - 1;
q = pStart + textLen - 1;
if (mungePeriods) {
*q-- = LF;
*q-- = CR;
*q-- = '.';
while (p >= pStart) {
*q-- = LF;
*q-- = CR;
p--;
while (*p != CR && p >= pStart) *q-- = *p--;
if (*(p+1) == '.') *q-- = '.';
}
} else {
while (p >= pStart) {
if (*p == CR) *q-- = LF;
*q-- = *p--;
}
}
return noErr;
}
/*----------------------------------------------------------------------------
MungeIn
Munge a block of text after receiving it from a server.
Entry: text = handle to ASCII text lines as received from server.
Warning: the memory block is modified by the function.
The memory block must be unlocked and nonpurgeable.
textLen = number of characters in text block.
mungePeriods = true to munge periods.
Exit: function result = error code.
CRLF line terminators are translated to CR. A final CR is added at the
end of the last line if necessary.
If mungePeriods=true, leading ".." characters on lines are mapped to
".", and the terminating "." on a line by itself is removed from the end
of the text block.
----------------------------------------------------------------------------*/
static OSErr MungeIn (Handle text, long textLen, Boolean mungePeriods)
{
OSErr err = noErr;
char *pEnd, *p, *q;
Boolean needTerminatingCR = false;
if (mungePeriods && textLen >= 3) {
p = *text + textLen - 3;
if (*p == '.' && *(p+1) == CR && *(p+2) == LF) textLen -= 3;
}
p = q = *text;
pEnd = p + textLen;
if (mungePeriods) {
while (p < pEnd) {
if (*p == '.' && *(p+1) == '.') {
*q++ = '.';
p += 2;
}
while (p < pEnd && (*p != CR || *(p+1) != LF)) *q++ = *p++;
*q++ = CR;
p += 2;
}
} else {
while (p < pEnd) {
if (*p == CR && *(p+1) == LF) {
*q++ = CR;
p += 2;
} else {
*q++ = *p++;
}
}
}
textLen = q - *text;
if (textLen > 0 && *(q-1) != CR) {
textLen++;
needTerminatingCR = true;
}
err = MySetHandleSize(text, textLen);
if (err != noErr) return err;
if (needTerminatingCR) *(*text + textLen - 1) = CR;
return noErr;
}
/*----------------------------------------------------------------------------
NetInit
Initialize the net.c module. This function must be called before calling
any other functions in the module.
Entry: giveTime = pointer to give time function.
log = pointer to logging function, or nil if none.
numBuffs = number of stream buffers to preallocate.
Exit: function result = error code.
The give time function is called repeatedly during all network
operations. It must be declared as follows:
OSErr GiveTime (void)
This function gives time to other processes/threads by calling WaitNextEvent/
YieldTo[Any]Thread, and may check for user cancel operations (e.g., clicking
a Cancel button or pressing Command-period or Escape). The function returns
noErr to continue the operation, or any other error code to cancel (abort)
the operation. Any error code returned by GiveTime is in turn returned as the
function result of the functions exported by net.c.
The net.c module is reentrant with respect to the GiveTime function. That is,
multiple threads of execution may make calls to the net.c module and use
the YieldTo[Any]Thread call in the GiveTime function.
The logging function is called once for each server command and response
and stream open/close operation. It must be declared as follows:
void Log (char logEntryType, unsigned long serverAddr,
unsigned short serverPort, unsigned short localPort, char *str)
You can use this function to log all sever commands and responses. On
entry to the Log function, the parameters are:
logEntryType =
'C' if command.
'R' if response.
' ' if open/close operation.
serverAddr = IP address of server.
serverPort = port number on server.
localPort = local port number.
str = command or response string or open/close message string.
The logging function is responsible for filtering out any passwords
in the strings.
You must call the InitMemUtil function in memutil.c to initialize the
memory management utilities *before* calling NetInit.
----------------------------------------------------------------------------*/
OSErr NetInit (NetGiveTimeFunction giveTime, NetLogFunction log, short numBuffs)
{
short i;
TStreamPtr s;
OSErr err = noErr;
long mtu;
gHaveOT = NetHaveOT();
gGiveTime = giveTime;
gLog = log;
if (gHaveOT) {
err = InitOpenTransport();
if (err != noErr) return netOpenDriverErr;
gStreamBufSize = sizeof(TStream);
} else {
gMacTCPDNRResultProcUPP = NewResultProc(MacTCPDNRResultProc);
gMacTCPCloseStreamCompletionRoutineUPP =
NewTCPIOCompletionProc(MacTCPCloseStreamCompletionRoutine);
err = OpenDriver("\p.IPP", &gRefNum);
if (err != noErr) return netOpenDriverErr;
mtu = GetMaxTCPMTU();
gTCPBufSize = 8 * mtu;
if (gTCPBufSize < kMinTCPBufSize) gTCPBufSize = kMinTCPBufSize;
gStreamBufSize = sizeof(TStream) - 1 + gTCPBufSize;
PatchExitToShell();
}
for (i = 0; i < numBuffs; i++) {
err = MyNewPtr(gStreamBufSize, &s);
if (err != noErr) return err;
s->nextFree = gFree;
gFree = s;
}
return noErr;
}
/*----------------------------------------------------------------------------
NetIdle
Handle idle time tasks. You must call this function in your main
event loop.
Exit: function result = error code.
This function takes care of releasing and deallocating streams which
have finished closing asynchronously in the background.
----------------------------------------------------------------------------*/
OSErr NetIdle (void)
{
TStreamPtr s, nextClose, prev = nil;
OSErr err = noErr;
for (s = gClose; s != nil; s = nextClose) {
nextClose = s->nextClose;
if (s->release) {
err = DoTCPRelease(s);
if (prev == nil) {
gClose = nextClose;
} else {
prev->nextClose = nextClose;
}
DisposeStreamBuffer(s);
} else {
prev = s;
}
}
return noErr;
}
/*----------------------------------------------------------------------------
NetTerm
Terminate the module. You must call this function when you quit.
Exit: function result = error code.
This functions waits until all asynchronous stream close operations have
completed, or for 5 seconds, whichever happens first. If any close
operations are still pending after 5 seconds, they are aborted.
In addition, any other open streams are aborted.
----------------------------------------------------------------------------*/
OSErr NetTerm (void)
{
TStreamPtr s;
long waitTil;
waitTil = TickCount() + 300;
while (gClose != nil && TickCount() < waitTil) {
(*gGiveTime)();
NetIdle();
}
for (s = gAll; s != nil; s = s->next) DoTCPRelease(s);
while (gFree != nil) {
s = gFree->next;
MyDisposePtr(gFree);
gFree = s;
}
gFree = gClose = gAll = nil;
if (gHaveOT) CloseOpenTransport();
return noErr;
}
/*----------------------------------------------------------------------------
NetOpen
Open a stream.
Entry: addr = IP address of server.
port = port number of service.
getHello = true to wait for, receive, and return an initial
"hello" message.
Exit: function result = error code.
*stream = opened stream reference.
*responseCode = server hello message response code.
response = C-format hello message string, including the
response code.
Hello message comment lines are discarded (response lines with a
'-' immediately following the response code).
----------------------------------------------------------------------------*/
OSErr NetOpen (unsigned long addr, unsigned short port, Boolean getHello,
NetStreamRef *stream, long *responseCode, CStr255 response)
{
TStreamPtr s;
OSErr err = noErr;
err = NewStreamBuffer(&s);
if (err != noErr) return err;
s->in = s->out = s->buf;
s->serverAddr = addr;
s->serverPort = port;
*s->command = *s->response = s->responseCode = 0;
err = DoTCPCreate(s);
if (err != noErr) goto exit2;
err = DoTCPActiveOpen(s, addr, port);
if (err != noErr) goto exit1;
if (getHello) {
err = ReadResponse(s, responseCode, response);
if (err != noErr) goto exit1;
}
*stream = (NetStreamRef)s;
return noErr;
exit1:
if (gHaveOT) {
if (s->otherSideHasClosed) err = netOpenStreamErr;
} else {
if (err == connectionClosing || err == connectionDoesntExist ||
err == connectionTerminated) err = netOpenStreamErr;
}
DoTCPRelease(s);
exit2:
DisposeStreamBuffer(s);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetClose
Close a stream.
Entry: stream = stream reference.
Exit: function result = error code.
A QUIT command is sent to the server before closing the stream.
Thus you do not need to and should not send this command yourself.
To improve performance, all streams are closed asynchronously.
This function returns immediately without any delay.
This asynchronous stream closing feature also permits you to close
connections in the background without interfering with or delaying user
actions in the foreground.
----------------------------------------------------------------------------*/
OSErr NetClose (NetStreamRef stream)
{
TStreamPtr s;
OSErr err = noErr;
s = (TStreamPtr)stream;
s->closing = true;
/* Link the stream into the queue of closing streams. */
s->nextClose = gClose;
gClose = s;
/* Send the QUIT command. */
if (gHaveOT) {
if (s->weHaveClosed && s->otherSideHasClosed) {
s->release = true;
return noErr;
} else {
err = OTSetBlocking(s->ref);
if (err != noErr) goto exit;
err = DoTCPSend(s, "QUIT\r\n", 6, true);
if (err != noErr) goto exit;
err = OTSetNonBlocking(s->ref);
if (err != noErr) goto exit;
}
} else {
s->wds[0].length = 6;
s->wds[0].ptr = "quit\r\n";
InitMacTCPParamBlock(&s->pBlock, TCPSend);
s->pBlock.csParam.send.wdsPtr = (Ptr)s->wds;
s->pBlock.ioCompletion = gMacTCPCloseStreamCompletionRoutineUPP;
s->pBlock.tcpStream = s->tcpStream;
err = PBControlAsync((ParmBlkPtr)&s->pBlock);
if (err != noErr) goto exit;
}
if (gLog != nil) {
LogMessage(s, 'C', "QUIT");
LogMessage(s, ' ', "Asynch stream close initiated.");
}
return noErr;
exit:
s->release = true;
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetCommand
Send a command to a server and get the response.
Entry: stream = stream reference.
command = C-format command string.
Exit: function result = error code.
*responseCode = server response code.
response = C-format server response string, including the
response code.
Response comment lines are discarded (response lines with a
'-' immediately following the response code).
----------------------------------------------------------------------------*/
OSErr NetCommand (NetStreamRef stream, char *command,
long *responseCode, CStr255 response)
{
TStreamPtr s;
OSErr err = noErr;
s = (TStreamPtr)stream;
err = SendCommand(s, command);
if (err != noErr) goto exit;
err = ReadResponse(s, responseCode, response);
if (err != noErr) goto exit;
return noErr;
exit:
DoTCPRelease(s);
DisposeStreamBuffer(s);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetGetExtraResponse
Get an extra command response from a server.
Entry: stream = stream reference.
Exit: function result = error code.
*responseCode = server response code.
response = C-format server response string, including the
response code.
Response comment lines are discarded (response lines with a
'-' immediately following the response code).
Some servers have commands which return more than one response. The
NetCommand function above reads the first response. This function is
used to read the additional response(s). The FTP RETR and STOR commands
are examples.
----------------------------------------------------------------------------*/
OSErr NetGetExtraResponse (NetStreamRef stream, long *responseCode,
CStr255 response)
{
TStreamPtr s;
OSErr err = noErr;
s = (TStreamPtr)stream;
err = ReadResponse(s, responseCode, response);
if (err != noErr) goto exit;
return noErr;
exit:
DoTCPRelease(s);
DisposeStreamBuffer(s);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetBatchedCommands
Send multiple batched commands to a server and get and process
the responses.
Entry: stream = stream reference.
commands = handle to CR-terminated commands.
Warning: the memory block is modified by the function.
The memory block must be unlocked and nonpurgeable.
doOneResponse = pointer to response processing function.
userDataPtr = pointer to user data to be passed through
to the doOneResponse function.
Exit: function result = error code.
The response processing function must be declared as follows:
void DoOneResponse (long responseCode, CStr255 response, Ptr userDataPtr)
The function is called once for each command response. It is passed the
following parameters:
responseCode = server response code.
response = C-format server response string, including the
response code.
userDataPtr = user data pointer.
Response comment lines are discarded (leading response lines with a
'-' immediately following the response code).
CR line terminators are mapped to CRLF line terminators before sending
the batched commands.
When you know in advance that you need to send a number of commands to a
server all in a row, it is usually much more efficient to send them in
a batch rather than sending them one at a time using the NetCommand function
above. You should take care, however, since not all servers support batched
commands.
All of the batched commands must return only a single response (after
skipping any possible comment lines).
----------------------------------------------------------------------------*/
OSErr NetBatchedCommands (NetStreamRef stream, Handle commands,
NetDoOneResponse doOneResponse, Ptr userDataPtr)
{
TStreamPtr s;
OSErr err = noErr;
char *p, *q, *r;
long cmdLen;
unsigned short len;
long responseCode;
CStr255 response;
short numCmds, i;
char state;
state = MyHGetState(commands);
err = MungeOut(commands, false);
if (err != noErr) goto exit;
cmdLen = MyGetHandleSize(commands);
s = (TStreamPtr)stream;
MyHLock(commands);
p = *commands;
while (cmdLen > 0) {
len = cmdLen > 4000 ? 4000 : cmdLen;
q = p + len - 1;
while (q > p && (*q != LF || *(q-1) != CR)) q--;
if (q == p) {
p += len;
cmdLen -= len;
continue;
} else {
numCmds = 0;
len = q - p + 1;
while (q > p) {
if (*q == LF && *(q-1) == CR) {
numCmds++;
q -= 2;
} else {
q--;
}
}
}
err = DoTCPSend(s, StripAddress(p), len, true);
if (err != noErr) goto exit;
if (gLog != nil) {
q = p;
for (i = 0; i < numCmds; i++) {
r = q;
while (*r != CR || *(r+1) != LF) r++;
*r = 0;
LogMessage(s, 'C', q);
q = r + 2;
}
}
p += len;
cmdLen -= len;
while (numCmds--) {
err = ReadResponse(s, &responseCode, response);
if (err != noErr) goto exit;
(*doOneResponse)(responseCode, response, userDataPtr);
}
}
MyHSetState(commands, state);
return noErr;
exit:
DoTCPRelease(s);
DisposeStreamBuffer(s);
MyHSetState(commands, state);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetPutText
Send command text to a server.
Entry: stream = stream reference.
text = handle to CR-terminated ASCII text lines.
Warning: the memory block is modified by the function.
The memory block must be unlocked and nonpurgeable.
Exit: function result = error code.
CR line terminators are translated to CRLF. A terminating "." on a line
by itself is sent. The caller should not include this terminating line
in the "text" block. Leading "." characters on lines are mapped to "..".
If the text block does not have a terminating CR at the end of the last
line, one is added.
----------------------------------------------------------------------------*/
OSErr NetPutText (NetStreamRef stream, Handle text)
{
TStreamPtr s;
OSErr err = noErr;
char *p;
long textLen;
unsigned short len;
char state;
state = MyHGetState(text);
err = MungeOut(text, true);
if (err != noErr) goto exit;
textLen = MyGetHandleSize(text);
s = (TStreamPtr)stream;
MyHLock(text);
p = *text;
while (textLen > 0) {
len = textLen > 4000 ? 4000 : textLen;
err = DoTCPSend(s, StripAddress(p), len, textLen == len);
if (err != noErr) goto exit;
p += len;
textLen -= len;
}
MyHSetState(text, state);
return noErr;
exit:
DoTCPRelease(s);
DisposeStreamBuffer(s);
MyHSetState(text, state);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetGetText
Get response text from a server.
Entry: stream = stream reference.
text = pointer to handle in which to return text,
or nil if none.
chunkFunction = pointer to chunk processing function,
or nil if none.
userDataPtr = pointer to user data to be passed through
to the chunk processing function, or nil if none.
Exit: function result = error code.
*text = handle to received ASCII text lines, if text != nil.
In the text returned via the "text" handle (if requested),
CRLF line terminators are translated to CR. The server must send a
terminating "." on a line by itself. This terminating line is not
included in the "text" handle returned to the caller. Leading
".." characters on lines are mapped to ".".
The chunk processing function, if any, is called every time a new
block of text is received from the server. This function must be
declared as follows:
OSErr ProcessTextChunk (Ptr t, long tLen, Ptr userDataPtr,
long *truncPos)
Entry: t = pointer to raw text received from server.
tLen = length of text received from server.
userDataPtr = pointer to user data.
Exit: function result = error code.
*truncPos = position at which to truncate text if
error code is netTruncatedErr.
If text == nil, the text is processed by the chunk processing function
in pieces (e.g., saved to a file as it comes in over the network). In
this case, the text is not accumulated and returned as a whole to the
NetGetText caller, and the "t" and "tLen" parameters passed to the
chunk processing function are for just the chunk received.
If text != nil, the text is accumulated as it is received and returned
as a whole to the NetGetText caller, and the "t" and "tLen" parameters
passed to the chunk processing function (if any) are for the entire text
as accumulated so far.
If the chunk processing function returns with an error code, NetGetText
aborts the stream and returns to the caller immediately with the text handle
set to just the text received so far. The error code returned by the
chunk processing function is returned to the NetGetText caller as NetGetText's
function result.
If the chunk processing function returns netTruncatedErr, and if text != nil,
the returned text is truncated to "truncPos" bytes, as specified by the
chunk processing function, and NetGetText returns the error code netTruncatedErr
to its caller.
The text passed to the chunk processing function is raw. It contains CRLF
line terminators and doubled leading ".." characters. The terminating "."
on a line by itself, however, is not passed to the chunk processing function.
----------------------------------------------------------------------------*/
OSErr NetGetText (NetStreamRef stream, Handle *text,
NetChunkFunction chunkFunction, Ptr userDataPtr)
{
TStreamPtr s;
OSErr err = noErr;
OSErr chunkErr = noErr;
Handle t = nil;
long tLen = 0;
long tAllocated = 0;
unsigned short len;
char *tEnd;
long truncPos;
s = (TStreamPtr)stream;
tLen = s->in - s->out;
tAllocated = tLen + 4005;
err = MyNewHandle(tAllocated, &t);
if (err != noErr) goto exit;
if (tLen > 0) BlockMoveData(s->out, *t, tLen);
s->in = s->out = s->buf;
while (true) {
if (tLen >= 3) {
tEnd = *t + tLen;
if (*(tEnd-3) == '.' && *(tEnd-2) == CR && *(tEnd-1) == LF) {
if (tLen == 3) break;
if (tLen >= 5 && *(tEnd-5) == CR && *(tEnd-4) == LF) break;
}
}
len = 4000;
MyHLock(t);
err = DoTCPRcv(s, *t + tLen, &len);
if (err != noErr) goto exit;
MyHUnlock(t);
tLen += len;
if (chunkFunction != nil && tLen >= 5) {
MyHLock(t);
chunkErr = (*chunkFunction)(*t, tLen - 5, userDataPtr, &truncPos);
MyHUnlock(t);
if (chunkErr != noErr) goto exit2;
}
if (text == nil) {
BlockMoveData(*t + tLen - 5, *t, 5);
tLen = 5;
} else {
if (tAllocated - tLen <= 4000) {
tAllocated += 4000;
err = MySetHandleSize(t, tAllocated);
if (err != noErr) goto exit;
}
}
}
if (chunkFunction != nil && tLen > 3) {
MyHLock(t);
chunkErr = (*chunkFunction)(*t, tLen-3, userDataPtr, &truncPos);
MyHUnlock(t);
if (chunkErr != noErr) goto exit2;
}
if (text == nil) {
MyDisposeHandle(t);
} else {
err = MungeIn(t, tLen, true);
if (err != noErr) goto exit;
*text = t;
}
return noErr;
exit:
DoTCPRelease(s);
DisposeStreamBuffer(s);
MyDisposeHandle(t);
return TranslateErrorCode(err);
exit2:
if (text == nil) {
MyDisposeHandle(t);
} else {
if (chunkErr == netTruncatedErr) tLen = truncPos;
err = MungeIn(t, tLen, true);
if (err != noErr) goto exit;
*text = t;
}
DoTCPRelease(s);
DisposeStreamBuffer(s);
return chunkErr;
}
/*----------------------------------------------------------------------------
NetFTPDataPassiveOpen
Open an FTP passive data stream.
Exit: function result = error code.
*port = assigned unused local port number.
*stream = reference to opened FTP data stream.
This function returns immediately. It should be followed by a call to
NetFTPDataWaitForConnection to wait for the FTP server to open its end of
the data stream connection.
It's confusing, but this "passive" stream open function is used for
*active* FTP mode data transfers (the client opens a passive stream
and waits for the server to connect). For *passive* FTP mode data
transfers (the server opens a passive stream and waits for the client
to connect), the regular "active" stream open function NetOpen must
be used instead.
----------------------------------------------------------------------------*/
OSErr NetFTPDataPassiveOpen (unsigned short *port, NetStreamRef *stream)
{
TStreamPtr s;
OSErr err = noErr;
err = NewStreamBuffer(&s);
if (err != noErr) goto exit2;
s->in = s->out = s->buf;
err = DoTCPCreate(s);
if (err != noErr) goto exit2;
err = DoTCPPassiveOpen(s, port);
if (err != noErr) goto exit1;
*stream = (NetStreamRef)s;
return noErr;
exit1:
DoTCPRelease(s);
exit2:
DisposeStreamBuffer(s);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetFTPDataClose
Close an FTP data stream.
Entry: stream = FTP data stream reference.
Exit: function result = error code.
This function is used to close all FTP data streams, including "passive
mode" data streams and "active mode" data streams.
----------------------------------------------------------------------------*/
OSErr NetFTPDataClose (NetStreamRef stream)
{
TStreamPtr s;
OSErr err = noErr;
s = (TStreamPtr)stream;
s->closing = true;
/* Link the stream into the queue of closing streams. */
s->nextClose = gClose;
gClose = s;
/* Close our end of the stream. */
if (gHaveOT) {
if (!s->weHaveClosed) {
err = OTSndOrderlyDisconnect(s->ref);
if (err != noErr) goto exit;
s->weHaveClosed = true;
} else if (s->weHaveClosed && s->otherSideHasClosed) {
s->release = true;
}
return noErr;
} else {
/* Start an asynchronous close operation, chained to our
completion routine. */
InitMacTCPParamBlock(&s->pBlock, TCPClose);
s->pBlock.ioCompletion = gMacTCPCloseStreamCompletionRoutineUPP;
s->pBlock.tcpStream = s->tcpStream;
err = PBControlAsync((ParmBlkPtr)&s->pBlock);
if (err != noErr) goto exit;
if (gLog != nil) LogMessage(s, ' ', "Asynch stream close initiated.");
return noErr;
}
exit:
s->release = true;
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetFTPDataWaitForConnection
Wait for an FTP server to open its end of a passive data stream.
Entry: stream = FTP data stream reference.
Exit: function result = error code.
It's confusing, but this "passive" stream wait function is used for
*active* FTP mode data transfers (the client opens a passive stream
and waits for the server to connect). For *passive* FTP mode data
transfers (the server opens a passive stream and waits for the client
to connect), the regular "active" stream open function NetOpen must
be used instead, and this function is not used.
----------------------------------------------------------------------------*/
OSErr NetFTPDataWaitForConnection (NetStreamRef stream)
{
TStreamPtr s;
OSErr err = noErr;
InetAddress *inetAddr;
s = (TStreamPtr)stream;
if (gHaveOT) {
while (true) {
err = (*gGiveTime)();
if (err != noErr) goto exit;
err = OTListen(s->ref, s->call);
if (err == noErr) break;
if (err != kOTNoDataErr) goto exit;
}
inetAddr = (InetAddress*)s->call->addr.buf;
s->serverAddr = inetAddr->fHost;
s->serverPort = inetAddr->fPort;
s->complete = false;
err = OTAccept(s->ref, s->ref, s->call);
if (err != noErr) goto exit;
err = MyOTStreamWait(s, true);
if (err != noErr) goto exit;
} else {
while (s->pBlock.ioResult > 0) {
err = (*gGiveTime)();
if (err != noErr) goto exit;
}
err = s->pBlock.ioResult;
if (err != noErr) goto exit;
s->serverAddr = s->pBlock.csParam.open.remoteHost;
s->serverPort = s->pBlock.csParam.open.remotePort;
}
if (gLog != nil) LogMessage(s, ' ', "Stream opened.");
return noErr;
exit:
if (gHaveOT) {
if (s->otherSideHasClosed) err = netOpenStreamErr;
} else {
if (err == connectionClosing || err == connectionDoesntExist ||
err == connectionTerminated) err = netOpenStreamErr;
}
DoTCPRelease(s);
DisposeStreamBuffer(s);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetGetFTPData
Get data from an FTP server.
Entry: stream = FTP data stream reference.
mapCR = true to map CRLF line terminators to CR.
Exit: function result = error code.
*data = handle to received data.
----------------------------------------------------------------------------*/
OSErr NetGetFTPData (NetStreamRef stream, Boolean mapCR, Handle *data)
{
TStreamPtr s;
OSErr err = noErr;
Handle d = nil;
long dLen = 0;
long dAllocated = 4000;
long numFree;
unsigned short len;
s = (TStreamPtr)stream;
err = MyNewHandle(dAllocated, &d);
if (err != noErr) goto exit;
while (true) {
numFree = dAllocated - dLen;
if (numFree <= 4000) {
dAllocated += 4000;
err = MySetHandleSize(d, dAllocated);
if (err != noErr) goto exit;
}
len = 4000;
MyHLock(d);
err = DoTCPRcv(s, *d + dLen, &len);
MyHUnlock(d);
if (err == netLostConnectionErr) break;
if (err != noErr) goto exit;
dLen += len;
}
if (mapCR) {
err = MungeIn(d, dLen, false);
if (err != noErr) goto exit;
} else {
MySetHandleSize(d, dLen);
}
*data = d;
return noErr;
exit:
DoTCPRelease(s);
DisposeStreamBuffer(s);
MyDisposeHandle(d);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetPutFTPData
Send data to an FTP server.
Entry: stream = FTP data stream reference.
mapCR = true to map CR line terminators to CRLF.
data = handle to data.
Exit: function result = error code.
Warning: If mapCR = true, the data block is modified by the function,
and it must be unlocked and nonpurgeable.
----------------------------------------------------------------------------*/
OSErr NetPutFTPData (NetStreamRef stream, Boolean mapCR, Handle data)
{
TStreamPtr s;
OSErr err = noErr;
char *p;
long dataLen;
unsigned short len;
char state;
state = MyHGetState(data);
if (mapCR) {
err = MungeOut(data, false);
if (err != noErr) goto exit;
}
dataLen = MyGetHandleSize(data);
s = (TStreamPtr)stream;
MyHLock(data);
p = *data;
while (dataLen > 0) {
len = dataLen > 4000 ? 4000 : dataLen;
err = DoTCPSend(s, StripAddress(p), len, dataLen == len);
if (err != noErr) goto exit;
p += len;
dataLen -= len;
}
MyHSetState(data, state);
return noErr;
exit:
DoTCPRelease(s);
DisposeStreamBuffer(s);
MyHSetState(data, state);
return TranslateErrorCode(err);
}
/*----------------------------------------------------------------------------
NetGetMyAddr
Get this Mac's IP address.
Exit: function result = error code.
*addr = the IP address of this Mac.
With Open Transport, if the Mac has more than one IP interface, the
IP address of the default interface is returned.
----------------------------------------------------------------------------*/
OSErr NetGetMyAddr (unsigned long *addr)
{
struct GetAddrParamBlock pBlock;
OSErr err = noErr, giveTimeErr = noErr;
static Boolean gotIt = false;
static unsigned long myAddr;
InetInterfaceInfo ifaceInfo;
if (!gotIt) {
if (gHaveOT) {
err = OTInetGetInterfaceInfo(&ifaceInfo, kDefaultInetInterface);
if (err != noErr) return TranslateErrorCode(err);
myAddr = ifaceInfo.fAddress;
} else {
memset(&pBlock, 0, sizeof(pBlock));
pBlock.ioResult = 1;
pBlock.csCode = ipctlGetAddr;
pBlock.ioCRefNum = gRefNum;
PBControlAsync((ParmBlkPtr)&pBlock);
while (pBlock.ioResult > 0) {
giveTimeErr = (*gGiveTime)();
if (err == noErr) err = giveTimeErr;
}
if (err != noErr) return TranslateErrorCode(err);
err = pBlock.ioResult;
if (err != noErr) return TranslateErrorCode(err);
myAddr = pBlock.ourAddress;
}
gotIt = true;
}
*addr = myAddr;
return noErr;
}
/*----------------------------------------------------------------------------
NetGetMyAddrStr
Get this Mac's IP address as a dotted-decimal string
Exit: function result = error code.
name = this Mac's IP address, as a C-format string.
You must allocate at least 16 bytes for this string.
The returned string has max length 15.
----------------------------------------------------------------------------*/
OSErr NetGetMyAddrStr (char *addrStr)
{
unsigned long addr;
OSErr err = noErr;
static char theAddrStr[16];
static Boolean gotIt=false;
if (!gotIt) {
err = NetGetMyAddr(&addr);
if (err != noErr) return TranslateErrorCode(err);
sprintf(theAddrStr, "%ld.%ld.%ld.%ld",
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
addr & 0xff);
gotIt = true;
}
strcpy(addrStr, theAddrStr);
return noErr;
}
/*----------------------------------------------------------------------------
NetGetMyName
Get this Mac's domain name, if any.
Exit: function result = error code.
name = domain name of this Mac, as a C-format string.
----------------------------------------------------------------------------*/
OSErr NetGetMyName (CStr255 name)
{
unsigned long addr;
short len;
static OSErr err = noErr;
static Boolean gotIt=false;
static CStr255 theName;
if (!gotIt) {
err = NetGetMyAddr(&addr);
if (err != noErr) return TranslateErrorCode(err);
err = NetAddrToName(addr, theName);
if (err != noErr) return TranslateErrorCode(err);
gotIt = true;
len = strlen(theName);
if (theName[len-1] == '.') theName[len-1] = 0;
}
strcpy(name, theName);
return noErr;
}
/*----------------------------------------------------------------------------
NetNameToAddr
Translate a domain name to an IP address.
Entry: name = C-format domain name string, optionally followed by a
comma, space, or colon and then the port number.
defaultPort = default port number.
Exit: function result = error code.
*addr = IP address.
*port = port number.
----------------------------------------------------------------------------*/
OSErr NetNameToAddr (char *name, unsigned short defaultPort,
unsigned long *addr, unsigned short *port)
{
OSErr err = noErr, giveTimeErr = noErr;
short i;
static struct {
CStr255 domainName;
unsigned long addr;
} cache[10];
static short numCache = 0;
struct hostInfo hInfoMacTCP;
InetHostInfo hInfoOT;
Boolean done = false;
CStr255 domainName;
char *p, *q;
TMyOTInetSvcInfo svcInfo;
p = name;
q = domainName;
while (*p != 0 && *p != ',' && *p != ' ' && *p != ':') *q++ = *p++;
*q = 0;
q = p;
while (*q == ' ') q++;
if (*q == 0) {
*port = defaultPort;
} else {
p++;
if (!isdigit(*p)) return netDNRErr;
q = p+1;
while (isdigit(*q)) q++;
while (*q == ' ') q++;
if (*q != 0) return netDNRErr;
*port = atoi(p);
}
for (i=0; i<numCache; i++) {
if (strcmp(domainName, cache[i].domainName) == 0) {
*addr = cache[i].addr;
return noErr;
}
}
if (gHaveOT) {
err = OTInetStringToHost(domainName, addr);
if (err != noErr) {
err = MyOTOpenInetServices(&svcInfo);
if (err == kEINVALErr) return netDNRErr;
if (err != noErr) return TranslateErrorCode(err);
svcInfo.complete = false;
err = OTInetStringToAddress(svcInfo.ref, domainName, &hInfoOT);
if (err == noErr) err = MyOTInetSvcWait(&svcInfo);
OTCloseProvider(svcInfo.ref);
if (err != noErr) {
if (err == kOTNoDataErr || err == kOTBadNameErr) err = netDNRErr;
return TranslateErrorCode(err);
}
*addr = hInfoOT.addrs[0];
}
} else {
while (gMacTCPDNROperationInProgress) {
/* Some other thread is using the DNR. Wait for it to finish. */
err = (*gGiveTime)();
if (err != noErr) return err;
}
err = OpenResolver(nil);
if (err != noErr) return TranslateErrorCode(err);
memset(&hInfoMacTCP, 0, sizeof(hInfoMacTCP));
gMacTCPDNROperationInProgress = true;
err = StrToAddr(domainName, &hInfoMacTCP, gMacTCPDNRResultProcUPP, (char*)&done);
if (err == cacheFault) {
err = noErr;
while (!done) {
giveTimeErr = (*gGiveTime)();
if (err == noErr) err = giveTimeErr;
}
if (err == noErr) err = hInfoMacTCP.rtnCode;
}
gMacTCPDNROperationInProgress = false;
(*gGiveTime)();
CloseResolver();
if (err != noErr) return TranslateErrorCode(err);
*addr = hInfoMacTCP.addr[0];
}
if (numCache < 10) {
strcpy(cache[numCache].domainName, domainName);
cache[numCache].addr = *addr;
numCache++;
}
return noErr;
}
/*----------------------------------------------------------------------------
NetAddrToName
Translate an IP address to a domain name.
Entry: addr = IP address.
Exit: function result = error code.
name = domain name, as a C-format string.
----------------------------------------------------------------------------*/
OSErr NetAddrToName (unsigned long addr, CStr255 name)
{
struct hostInfo hInfoMacTCP;
OSErr err = noErr, giveTimeErr = noErr;
Boolean done=false;
TMyOTInetSvcInfo svcInfo;
if (gHaveOT) {
err = MyOTOpenInetServices(&svcInfo);
if (err != noErr) return TranslateErrorCode(err);
svcInfo.complete = false;
err = OTInetAddressToName(svcInfo.ref, addr, name);
if (err == noErr) err = MyOTInetSvcWait(&svcInfo);
OTCloseProvider(svcInfo.ref);
if (err != noErr) {
if (err == kOTNoDataErr || err == kOTBadNameErr) err = netDNRErr;
return TranslateErrorCode(err);
}
return noErr;
} else {
while (gMacTCPDNROperationInProgress) {
/* Some other thread is using the DNR. Wait for it to finish. */
err = (*gGiveTime)();
if (err != noErr) return err;
}
err = OpenResolver(nil);
if (err != noErr) return TranslateErrorCode(err);
memset(&hInfoMacTCP, 0, sizeof(hInfoMacTCP));
gMacTCPDNROperationInProgress = true;
err = AddrToName(addr, &hInfoMacTCP, gMacTCPDNRResultProcUPP, (char*)&done);
if (err == cacheFault) {
err = noErr;
while (!done) {
giveTimeErr = (*gGiveTime)();
if (err == noErr) err = giveTimeErr;
}
if (err == noErr) err = hInfoMacTCP.rtnCode;
}
gMacTCPDNROperationInProgress = false;
(*gGiveTime)();
CloseResolver();
if (err != noErr) return TranslateErrorCode(err);
hInfoMacTCP.cname[254] = 0;
strcpy(name, hInfoMacTCP.cname);
return noErr;
}
}
/*----------------------------------------------------------------------------
NetMacTCPDNROperationInProgress
Find out whether there is a MacTCP DNR operation in progress.
Exit: function result = true if MacTCP DNR operation in progress.
----------------------------------------------------------------------------*/
Boolean NetMacTCPDNROperationInProgress (void)
{
return gMacTCPDNROperationInProgress;
}
/*----------------------------------------------------------------------------
NetGetServerErrInfo
Get server error information for a stream.
Entry: stream = stream reference.
Exit: *serverErrInfo = server error informaton.
----------------------------------------------------------------------------*/
void NetGetServerErrInfo (NetStreamRef stream, NetServerErrInfo *serverErrInfo)
{
TStreamPtr s;
s = (TStreamPtr)stream;
strcpy(serverErrInfo->command, s->command);
strcpy(serverErrInfo->response, s->response);
serverErrInfo->responseCode = s->responseCode;
}
/*----------------------------------------------------------------------------
NetGetStreamStats
Get stream stats (bytes in/out).
Entry: stream = stream reference.
Exit: *bytesIn = number of bytes received on stream.
*bytesOut = number of bytes sent on stream.
----------------------------------------------------------------------------*/
void NetGetStreamStats (NetStreamRef stream, long *bytesIn, long *bytesOut)
{
TStreamPtr s;
s = (TStreamPtr)stream;
*bytesIn = s->bytesIn;
*bytesOut = s->bytesOut;
}
/*----------------------------------------------------------------------------
NetHaveOT
Determine whether we have Open Transport.
Exit: function result = true if Open Transport and Open Transport/TCP
are both installed.
Note: Apple says we shouldn't ship 68K OT code yet (not until OT 1.1).
So this function always returns false on 68K Macs for now.
----------------------------------------------------------------------------*/
Boolean NetHaveOT (void)
{
static Boolean gotIt = false;
static Boolean haveOT;
OSErr err = noErr;
long result;
#ifndef powerc
return false;
#endif
if (!gotIt) {
err = Gestalt(gestaltOpenTpt, &result);
haveOT = err == noErr &&
(result & gestaltOpenTptPresent) != 0 &&
(result & gestaltOpenTptTCPPresent) != 0;
gotIt = true;
}
return haveOT;
}